﻿using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate;
using NHibernate.Linq;
using NHibernate.Search;

namespace DreamDriven.ObjectModel
{
    /// <summary>
    /// Represents a common functionality of Repository objects.
    /// </summary>
    /// <typeparam name="TDataObject">Type of the entity.</typeparam>
    /// <typeparam name="TIdentifier">Type of the entity identifier.</typeparam>
    public abstract class DomainRepository<TDataObject, TIdentifier> : IDomainRepository
        where TDataObject : IDataObject
    {
        private readonly ISessionContainer _domainModel;

        protected internal virtual ISession Session
        {
            get { return _domainModel.Session; }
        }
         
        public virtual IQueryable<TDataObject> Collection
        {
            get
            {
                return Session.Query<TDataObject>();
            }
        }

        /// <summary>
        /// Persists data object into storage.
        /// This is transactional method.
        /// </summary>
        /// <typeparam name="TIdentifier">Type of identifier.</typeparam>
        /// <param name="dataObject"></param>
        /// <returns>Identifier of persisted data object.</returns>
        public virtual TIdentifier Create(TDataObject dataObject)
        {
            return (TIdentifier) Transact(() => Session.Save(dataObject));
        }

        public virtual void Delete(TDataObject dataObject)
        {
            Transact(() => Session.Delete(dataObject));
        }

        public virtual void Update(TDataObject dataObject)
        {
            Transact(() => Session.Update(dataObject));
        }

        public virtual TDataObject GetById(TIdentifier id)
        {
            return Transact(() => Session.Get<TDataObject>(id));
        }

        public virtual TDataObject UpdateDetached(TDataObject dataObject)
        {
            var attachedDataObject = Transact(() => (TDataObject) Session.Merge(dataObject));
            Transact(() => Session.Update(attachedDataObject));
            return attachedDataObject;
        }

        public virtual IList<TDataObject> GetAll()
        {
            return Transact(() => Collection.ToList());
        }

        public virtual int GetTotalCount()
        {
            return Transact(() => Collection.Count());
        }

        protected TResult Transact<TResult>(Func<TResult> func)
        {
            {
                if (!Session.Transaction.IsActive)
                {
                    TResult result;
                    using (var tx = Session.BeginTransaction())
                    {
                        result = func.Invoke();
                        tx.Commit();
                    }
                    return result;
                }
                return func.Invoke();
            }
        }

        protected void Transact(Action action)
        {
            Transact(() =>
            {
                action.Invoke();
                return false;
            });
        }

        public virtual IFullTextQuery CreateSearchQuery(string query)
        {
            using (IFullTextSession searchSession = NHibernate.Search.Search.CreateFullTextSession(Session))
            {
                return searchSession.CreateFullTextQuery<TDataObject>(query);
            }
        }

        public virtual IList<TDataObject> Search(string query)
        {
            using (IFullTextSession searchSession = NHibernate.Search.Search.CreateFullTextSession(Session))
            {
                return searchSession.CreateFullTextQuery<TDataObject>(query).List<TDataObject>();
            }
        }

        protected DomainRepository(IDomainModel domainModel)
        {
            _domainModel = domainModel;
        }
    }
}
